大概在之前的 monoid 的時候有提到過這個語法。
getProduct $ Product 1 `mappend` Product 2 `mappend` Product 3 -- 6
那時候我們說就類似取值的概念,那之所以 Product
會需要用 getProduct
來取值正是因為它使用了 newtype
來定義這個 type
newtype Product a = Product {getProduct :: a}
長得跟我們在使用 data type 的 record syntax 時很像,只是差異是 newtype
只能有一個 value constructor 且裡面也只能有一個 field 。
newtype IntList = IntList {getIntList:: [Int]} deriving (Eq, Show)
data IntList' = IntList' {getIntList':: [Int]} deriving (Eq, Show)
print $ IntList [1,2,3] -- IntList {getIntList = [1,2,3]}
print $ IntList' [1,2,3] -- IntList' {getIntList' = [1,2,3]}
print $ getIntList $ IntList [1,2,3] -- [1,2,3]
print $ getIntList' $ IntList' [1,2,3] -- [1,2,3]
print $ tail $ getIntList $ IntList [1,2,3] -- [2,3]
print $ head $ getIntList $ IntList [1,2,3] -- 1
當然我也可以替 newtype
所定義的 type 讓他成為其他 typeclass 的 instance
newtype IntWrapper = IntWrapper Int deriving (Eq, Show)
instance Semigroup IntWrapper where
(IntWrapper a) <> (IntWrapper b) = IntWrapper (a + b)
instance Monoid IntWrapper where
mempty = IntWrapper 0
print $ IntWrapper 1 <> IntWrapper 2 <> IntWrapper 3 -- IntWrapper 6
print $ mconcat [IntWrapper 1, IntWrapper 2, IntWrapper 3] -- IntWrapper 6
這邊我們實作了 IntWrapper
作為 Monoid
及 Semigroup
的 instance 的實作,經過這些實作我們就可以不用特地使用 pattern matching 來拆開 context 計算值。
所以 newtype
解決了什麼事情?基本上 newtype
是方便我們復用相似結構的 type 像是三角形的邊長以及三維座標一樣他們結構很像但實際上他們代表的意義完全不一樣,這也是為什麼它只有一個 value constructor 及 field 因為本意上就是想要讓開發者重新包裝一個 type 。
hmmm 今天突然想不到有什麼能寫所以只好挑一個有稍微提到但沒解釋過的語法來寫XD
今天的程式碼
https://github.com/toddLiao469469/30days-for-haskell
請問老師~
既然newtype
可以拿來包裝某個既有的type,那這樣為什麼不直接用data
就好了?
newtype
限制還一大堆,什麼只能有一個value constructor、一個filed的。
大大才真的算是老師吧 XD
不太確定我理解的對不對,但我想應該是因為 newtype
它的型別安全其實只有在編譯時才作用,這也讓他在 runtime 時可以比 data
快一點。
所以如果只是為了型別封裝之類的理由感覺就用 newtype
就好
題外話,我一直覺得 newtype 這個命名我蠻不能接受的,因為就語意上它更像是新建一個 type 而不是只在編譯時生效,然後 runtime 就變回基礎型別的感覺。